package com.box.app.arithmetic

import java.math.BigDecimal
import java.math.BigInteger
import java.math.RoundingMode
import kotlin.reflect.KType
import kotlin.reflect.jvm.jvmErasure

internal fun Number.toBigDecimal(): BigDecimal = if (this is BigDecimal) this else toString().toBigDecimal()

internal fun Number.toBigInteger(): BigInteger = if (this is BigInteger) this else toBigDecimal().toBigInteger()

actual operator fun Number.plus(other: Number): Number = toBigDecimal().add(other.toBigDecimal())

actual operator fun Number.minus(other: Number): Number = toBigDecimal().subtract(other.toBigDecimal())

actual operator fun Number.times(other: Number): Number = toBigDecimal().multiply(other.toBigDecimal())

actual operator fun Number.div(other: Number): Number {
    val dividend = toBigDecimal()
    val divisor = other.toBigDecimal()
    return dividend.divide(divisor, 1e5.toInt(), RoundingMode.HALF_EVEN)
}

actual operator fun Number.rem(other: Number): Number = toBigDecimal().remainder(other.toBigDecimal())

actual operator fun Number.unaryMinus(): Number = toBigDecimal().negate()

actual operator fun Number.inc(): Number = toBigDecimal().add(BigDecimal.ONE)

actual operator fun Number.dec(): Number = toBigDecimal().subtract(BigDecimal.ONE)

actual operator fun Number.inv(): Number = toBigInteger().not()

actual fun Number.plus(): Number = toBigDecimal().plus()

actual fun Number.negate(): Number = toBigDecimal().negate()

actual fun Number.pow(a: Number): Number = toBigDecimal().pow(a.toInt())

actual fun Number.and(a: Number): Number = toBigInteger().and(a.toBigInteger())

actual fun Number.or(a: Number): Number = toBigInteger().or(a.toBigInteger())

actual fun Number.xor(a: Number): Number = toBigInteger().xor(a.toBigInteger())

actual fun Number.shl(a: Number): Number = toBigInteger().shiftLeft(a.toInt())

actual fun Number.shr(a: Number): Number = toBigInteger().shiftRight(a.toInt())

actual fun Number.ushr(a: Number): Number = toInt().ushr(a.toInt())

actual fun String.toNumberOrNull(): Number? = toBigDecimalOrNull()

internal fun Number.to(type: KType): Number = when (type.jvmErasure) {
    Double::class -> toDouble()
    Float::class -> toFloat()
    Long::class -> toLong()
    Int::class -> toInt()
    Short::class -> toShort()
    Byte::class -> toByte()
    BigDecimal::class -> toBigDecimal()
    BigInteger::class -> toBigInteger()
    else -> throw TypeCastException()
}